package ace.module.weixin.core.impl.service.impl;

import ace.cmp.core.utils.SystemClockUtils;
import ace.module.weixin.api.constant.WeixinConstant;
import ace.module.weixin.core.impl.dao.entity.WxsMiniAppConfig;
import ace.module.weixin.core.impl.manager.WxsCacheManager;
import ace.module.weixin.core.impl.manager.WxsLockManager;
import ace.module.weixin.core.impl.manager.WxsMiniAppConfigManager;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

/**
 * 小程序数据库与缓存配置实现.
 *
 * @author Caspar
 */
@Slf4j
public class DbCacheWxMaConfig implements WxMaConfig {

  private static final String accessTokenCacheKeyName = "accesstoken";
  private static final String jsapiTicketCacheKeyName = "jsapiticket";
  private static final String cardApiTicketCacheKeyName = "cardapiticket";
  /**
   * 小程序应用id
   */
  private final String appId;
  private final WxsMiniAppConfigManager wxsMiniAppConfigManager;
  private final WxsLockManager wxsLockManager;
  private final WxsCacheManager wxsCacheManager;
  private final String accessTokenCacheKey;
  private final String accessTokenLockKey;
  private final String jsapiTicketCacheKey;
  private final String jsapiTicketLockKey;
  private final String cardApiTicketCacheKey;
  private final String cardApiTicketLockKey;

  private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;

  /**
   * .
   *
   * @param appId                   小程序应用id
   * @param wxsMiniAppConfigManager 小程序配置管理者
   * @param wxsLockManager          数据库与缓存锁管理者
   * @param wxsCacheManager         数据库与缓存管理者
   */
  public DbCacheWxMaConfig(String appId, WxsMiniAppConfigManager wxsMiniAppConfigManager,
      WxsLockManager wxsLockManager, WxsCacheManager wxsCacheManager) {
    this.wxsMiniAppConfigManager = wxsMiniAppConfigManager;
    this.wxsLockManager = wxsLockManager;
    this.wxsCacheManager = wxsCacheManager;
    this.appId = appId;
    this.accessTokenCacheKey = this.getCacheKey(appId, accessTokenCacheKeyName);
    this.accessTokenLockKey = this.getLockKey(appId, accessTokenCacheKeyName);
    this.jsapiTicketCacheKey = this.getCacheKey(appId, jsapiTicketCacheKeyName);
    this.jsapiTicketLockKey = this.getLockKey(appId, jsapiTicketCacheKeyName);
    this.cardApiTicketCacheKey = this.getCacheKey(appId, cardApiTicketCacheKeyName);
    this.cardApiTicketLockKey = this.getLockKey(appId, cardApiTicketCacheKeyName);
  }

  @Override
  public String getAccessToken() {
    return this.getWxAccessToken()
        .map(WxAccessToken::getAccessToken)
        .orElse(StringUtils.EMPTY);
  }

  @Override
  public Lock getAccessTokenLock() {
    return new DbLock(this.getAccessTokenLockKey(), this.wxsLockManager);
  }

  @Override
  public boolean isAccessTokenExpired() {
    Optional<WxAccessToken> wxAccessTokenOptional = this.getWxAccessToken();
    return wxAccessTokenOptional.map(
            wxAccessToken -> SystemClockUtils.currentTimeMillis() > wxAccessToken.getExpiresIn() * 1000L
        )
        .orElse(true);
  }

  @Override
  public void expireAccessToken() {
    this.updateAccessToken(StringUtils.EMPTY, 0);
  }

  @Override
  public void updateAccessToken(WxAccessToken accessToken) {
    this.saveAccessToken(accessToken);
  }

  @Override
  public void updateAccessToken(String accessToken, int expiresInSeconds) {
    WxAccessToken wxAccessToken = new WxAccessToken();
    wxAccessToken.setAccessToken(accessToken);
    wxAccessToken.setExpiresIn(expiresInSeconds);
    this.updateAccessToken(wxAccessToken);
  }

  @Override
  public String getJsapiTicket() {
    return this.getJsapiTicketFromDbOrCache().orElse(StringUtils.EMPTY);
  }

  @Override
  public Lock getJsapiTicketLock() {
    return new DbLock(this.getJsapiTicketLockKey(), this.wxsLockManager);
  }

  @Override
  public boolean isJsapiTicketExpired() {
    return StringUtils.isBlank(this.getJsapiTicketFromDbOrCache().orElse(StringUtils.EMPTY));
  }

  @Override
  public void expireJsapiTicket() {
    this.wxsCacheManager.saveOrUpdate(this.getJsapiTicketCacheKey(), 0, StringUtils.EMPTY);
  }

  @Override
  public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
    this.wxsCacheManager.saveOrUpdate(
        this.getJsapiTicketCacheKey(),
        expiresInSeconds * 1000L,
        jsapiTicket
    );
  }

  @Override
  public String getCardApiTicket() {
    return this.wxsCacheManager.getValueByKeyFromDbOrCache(
        this.getCardApiTicketCacheKey(),
        String.class
    ).orElse(StringUtils.EMPTY);
  }

  @Override
  public Lock getCardApiTicketLock() {
    return new DbLock(this.getCardApiTicketLockKey(), this.wxsLockManager);
  }

  @Override
  public boolean isCardApiTicketExpired() {
    return StringUtils.isBlank(
        this.wxsCacheManager.getValueByKeyFromDbOrCache(
            this.getCardApiTicketCacheKey(),
            String.class
        ).orElse(StringUtils.EMPTY)
    );
  }

  @Override
  public void expireCardApiTicket() {
    this.wxsCacheManager.saveOrUpdate(this.getCardApiTicketCacheKey(), 0, StringUtils.EMPTY);
  }

  @Override
  public void updateCardApiTicket(String apiTicket, int expiresInSeconds) {
    this.wxsCacheManager.saveOrUpdate(
        this.getCardApiTicketCacheKey(),
        expiresInSeconds * 1000L,
        apiTicket
    );
  }

  @Override
  public String getAppid() {
    return this.appId;
  }

  @Override
  public String getSecret() {
    return this.getWxsMiniAppConfig().map(WxsMiniAppConfig::getAppSecret).orElse(StringUtils.EMPTY);
  }

  @Override
  public String getToken() {
    return this.getWxsMiniAppConfig().map(WxsMiniAppConfig::getToken).orElse(StringUtils.EMPTY);
  }

  @Override
  public String getAesKey() {
    return this.getWxsMiniAppConfig().map(WxsMiniAppConfig::getAesKey).orElse(StringUtils.EMPTY);
  }

  @Override
  public String getOriginalId() {
    return this.getWxsMiniAppConfig()
        .map(WxsMiniAppConfig::getOriginalId)
        .orElse(StringUtils.EMPTY);
  }

  @Override
  public String getCloudEnv() {
    throw new NotImplementedException("不支持方法");
  }

  @Override
  public String getMsgDataFormat() {
    return this.getWxsMiniAppConfig().map(WxsMiniAppConfig::getMsgDataFormat)
        .orElse(StringUtils.EMPTY);
  }

  @Override
  public long getExpiresTime() {
    return this.getWxAccessToken().map(WxAccessToken::getExpiresIn).orElse(0);
  }

  @Override
  public String getHttpProxyHost() {
    return this.getWxsMiniAppConfig().map(WxsMiniAppConfig::getHttpProxyHost)
        .orElse(StringUtils.EMPTY);
  }

  @Override
  public int getHttpProxyPort() {
    return this.getWxsMiniAppConfig().map(WxsMiniAppConfig::getHttpProxyPort).orElse(0);
  }

  @Override
  public String getHttpProxyUsername() {
    return this.getWxsMiniAppConfig().map(WxsMiniAppConfig::getHttpProxyUsername)
        .orElse(StringUtils.EMPTY);
  }

  @Override
  public String getHttpProxyPassword() {
    return this.getWxsMiniAppConfig().map(WxsMiniAppConfig::getHttpProxyPassword)
        .orElse(StringUtils.EMPTY);
  }

  @Override
  public int getRetrySleepMillis() {
    return 1000;
  }

  @Override
  public int getMaxRetryTimes() {
    return 5;
  }

  @Override
  public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
    return apacheHttpClientBuilder;
  }

  @Override
  public boolean autoRefreshToken() {
    return false;
  }

  @Override
  public void setApiHostUrl(String apiHostUrl) {
    Optional<WxsMiniAppConfig> wxsMiniAppConfigOptional = this.getWxsMiniAppConfig();

    Assert.isTrue(wxsMiniAppConfigOptional.isPresent(), "wxsMiniAppConfig 不能为空");
    wxsMiniAppConfigOptional.get().setApiHostUrl(apiHostUrl);
    this.wxsMiniAppConfigManager.modify(wxsMiniAppConfigOptional.get());
  }

  @Override
  public String getApiHostUrl() {
    return Optional.ofNullable(
            this.wxsMiniAppConfigManager
                .getByAppIdFromDbOrCache(this.appId)
        )
        .map(WxsMiniAppConfig::getApiHostUrl)
        .orElse(StringUtils.EMPTY);
  }

  @Override
  public String getAccessTokenUrl() {
    return Optional.ofNullable(
            this.wxsMiniAppConfigManager
                .getByAppIdFromDbOrCache(this.appId)
        )
        .map(WxsMiniAppConfig::getAccessTokenUrl)
        .orElse(StringUtils.EMPTY);
  }

  @Override
  public void setAccessTokenUrl(String accessTokenUrl) {
    Optional<WxsMiniAppConfig> wxsMiniAppConfigOptional = this.getWxsMiniAppConfig();

    Assert.isTrue(wxsMiniAppConfigOptional.isPresent(), "wxsMiniAppConfig 不能为空");
    wxsMiniAppConfigOptional.get().setAccessTokenUrl(accessTokenUrl);
    this.wxsMiniAppConfigManager.modify(wxsMiniAppConfigOptional.get());
  }

  private Optional<WxsMiniAppConfig> getWxsMiniAppConfig() {
    return Optional.ofNullable(this.wxsMiniAppConfigManager.getByAppIdFromDbOrCache(this.appId));
  }

  private Optional<WxAccessToken> getWxAccessToken() {
    return this.wxsCacheManager.getValueByKeyFromDbOrCache(this.getAccessTokenCacheKey(),
        WxAccessToken.class);
  }

  private void saveAccessToken(WxAccessToken wxAccessToken) {
    this.wxsCacheManager.saveOrUpdate(
        this.getAccessTokenCacheKey(),
        wxAccessToken.getExpiresIn() * 1000L,
        wxAccessToken
    );
  }

  private String getCacheKey(String appId, String name) {
    return String.format("%s:%s:%s", WeixinConstant.MODULE_NAME, appId, name);
  }

  private String getLockKey(String appId, String name) {
    return String.format("%s_%s_%s", WeixinConstant.MODULE_NAME, appId, name);
  }

  private Optional<String> getJsapiTicketFromDbOrCache() {
    return this.wxsCacheManager.getValueByKeyFromDbOrCache(this.getJsapiTicketCacheKey(),
        String.class);
  }

  private String getAccessTokenCacheKey() {
    return this.accessTokenCacheKey;
  }

  private String getJsapiTicketCacheKey() {
    return this.jsapiTicketCacheKey;
  }

  private String getCardApiTicketCacheKey() {
    return this.cardApiTicketCacheKey;
  }

  private String getAccessTokenLockKey() {
    return this.accessTokenLockKey;
  }

  private String getJsapiTicketLockKey() {
    return this.jsapiTicketLockKey;
  }

  private String getCardApiTicketLockKey() {
    return this.cardApiTicketLockKey;
  }
}
